<script setup lang="ts">
import { ref, computed, onMounted } from 'vue'

type ResolverNode = {
  name: string
  ip: string
  version: string
  params: { retries?: number; max_ncache_ttl?: number }
}

type ForwarderNode = {
  name: string
  ip: string
  version: string
  params: { policy: string; backend_count: number }
  backends_detailed: ResolverNode[]
}

// 后端返回的数据类型
type BackendNodeInfo = {
  ip: string
  container_name: string
  max_ncache_ttl: string
  resolver_nonbackoff_tries: string
  image_version: string
}

type NodeInfo = {
  front_IP: string
  container_name: string
  server_policy: string
  image_version: string
  out_count: string
  backend: BackendNodeInfo[]
}

/* ---------------------- 节点数据 ---------------------- */
const forwarderNodes = ref<ForwarderNode[]>([])
const loadingNodes = ref(false)
const nodesError = ref<string | null>(null)

/* ---------------------- 从后端读取节点信息 ---------------------- */
async function loadNodesFromBackend() {
  loadingNodes.value = true
  nodesError.value = null

  try {
    const response = await fetch('/api/docker/nodes_info', {
      method: 'GET',
      headers: {
        'Accept': 'application/json'
      }
    })

    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`)
    }

    const responseData = await response.json()
    console.log('从后端获取的节点信息（原始）:', responseData)

    // 处理不同的响应格式：可能是数组，也可能是包装在对象中的数组
    let data: NodeInfo[] = []
    if (Array.isArray(responseData)) {
      data = responseData
    } else if (responseData.data && Array.isArray(responseData.data)) {
      data = responseData.data
    } else if (responseData.nodes && Array.isArray(responseData.nodes)) {
      data = responseData.nodes
    } else {
      throw new Error('后端返回的数据格式不正确，期望数组格式')
    }

    console.log('解析后的节点数据:', data)

    // 转换数据格式
    const convertedNodes: ForwarderNode[] = data.map((nodeInfo) => {
      // 转换后端节点列表
      const backends: ResolverNode[] = (nodeInfo.backend || []).map((backend) => ({
        name: backend.container_name || '',
        ip: backend.ip || '',
        version: backend.image_version || '',
        params: {
          retries: backend.resolver_nonbackoff_tries ? parseInt(backend.resolver_nonbackoff_tries, 10) : undefined,
          max_ncache_ttl: backend.max_ncache_ttl ? parseInt(backend.max_ncache_ttl, 10) : undefined
        }
      }))

      // 转换转发器节点
      return {
        name: nodeInfo.container_name || '',
        ip: nodeInfo.front_IP || '',
        version: nodeInfo.image_version || '',
        params: {
          policy: nodeInfo.server_policy || 'roundrobin',
          backend_count: backends.length
        },
        backends_detailed: backends
      }
    })

    forwarderNodes.value = convertedNodes
    console.log('转换后的节点数据:', convertedNodes)

    // 如果转换后没有节点，重置选中索引
    if (convertedNodes.length === 0) {
      selectedForwarderIndex.value = 0
    } else if (selectedForwarderIndex.value >= convertedNodes.length) {
      selectedForwarderIndex.value = 0
    }
  } catch (error) {
    console.error('读取节点信息失败:', error)
    nodesError.value = error instanceof Error ? error.message : '读取节点信息失败'
    // 发生错误时，保持空数组或使用默认数据
    forwarderNodes.value = []
  } finally {
    loadingNodes.value = false
  }
}

// 组件挂载时加载节点信息
onMounted(() => {
  loadNodesFromBackend()
})

/* ---------------------- 选中前端节点 & 统计 ---------------------- */

const selectedForwarderIndex = ref(0)

const selectedForwarder = computed<ForwarderNode | null>(() => {
  return forwarderNodes.value[selectedForwarderIndex.value] || null
})

const totalResolvers = computed(() => {
  if (!selectedForwarder.value) return 0
  return selectedForwarder.value.backends_detailed.length
})

/* ---------------------- 编辑态控制（转发器 + 解析器） ---------------------- */

const editingForwarder = ref(false)
const editingResolverMap = ref<Record<string, boolean>>({})

// 保存编辑前的原始值，用于取消时恢复
const originalForwarderValues = ref<{ policy?: string }>({})
const originalResolverValues = ref<Record<string, { retries?: number; max_ncache_ttl?: number }>>({})

function isResolverEditing(name: string) {
  return !!editingResolverMap.value[name]
}

function startForwarderEdit() {
  if (!selectedForwarder.value) return
  // 保存原始值
  originalForwarderValues.value = {
    policy: selectedForwarder.value.params.policy
  }
  editingForwarder.value = true
}

function cancelForwarderEdit() {
  if (!selectedForwarder.value) return
  // 恢复原始值
  if (originalForwarderValues.value.policy !== undefined) {
    selectedForwarder.value.params.policy = originalForwarderValues.value.policy
  }
  editingForwarder.value = false
  originalForwarderValues.value = {}
}

async function saveForwarderEdit() {
  if (!selectedForwarder.value) return

  try {
    const response = await fetch('/api/docker/update_drs_in_node', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Accept': 'application/json'
      },
      body: JSON.stringify({
        node_name: selectedForwarder.value.name,
        policy: selectedForwarder.value.params.policy
      })
    })

    if (!response.ok) {
      const errorText = await response.text()
      throw new Error(`HTTP error! status: ${response.status}, message: ${errorText}`)
    }

    const result = await response.json()
    console.log('更新转发器成功:', result)
    
    editingForwarder.value = false
    originalForwarderValues.value = {}
    
    // 显示成功提示
    alert('转发器更新成功')
  } catch (error) {
    console.error('更新转发器失败:', error)
    alert(`更新转发器失败: ${error instanceof Error ? error.message : '未知错误'}`)
    // 发生错误时恢复原始值
    cancelForwarderEdit()
  }
}

function startResolverEdit(name: string) {
  const resolver = selectedForwarder.value?.backends_detailed.find(r => r.name === name)
  if (!resolver) return
  
  // 保存原始值
  originalResolverValues.value[name] = {
    retries: resolver.params.retries,
    max_ncache_ttl: resolver.params.max_ncache_ttl
  }
  editingResolverMap.value = { ...editingResolverMap.value, [name]: true }
}

function cancelResolverEdit(name: string) {
  const resolver = selectedForwarder.value?.backends_detailed.find(r => r.name === name)
  if (resolver && originalResolverValues.value[name]) {
    // 恢复原始值
    const original = originalResolverValues.value[name]
    if (original.retries !== undefined) {
      resolver.params.retries = original.retries
    }
    if (original.max_ncache_ttl !== undefined) {
      resolver.params.max_ncache_ttl = original.max_ncache_ttl
    }
  }
  editingResolverMap.value = { ...editingResolverMap.value, [name]: false }
  delete originalResolverValues.value[name]
}

async function saveResolverEdit(name: string) {
  const resolver = selectedForwarder.value?.backends_detailed.find(r => r.name === name)
  if (!resolver) return

  try {
    const response = await fetch('/api/docker/update_drs_out_node', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Accept': 'application/json'
      },
      body: JSON.stringify({
        node_name: resolver.name,
        resolver_nonbackoff_tries: resolver.params.retries || 0,
        max_ncache_ttl: resolver.params.max_ncache_ttl || 0
      })
    })

    if (!response.ok) {
      const errorText = await response.text()
      throw new Error(`HTTP error! status: ${response.status}, message: ${errorText}`)
    }

    const result = await response.json()
    console.log('更新解析器成功:', result)
    
    editingResolverMap.value = { ...editingResolverMap.value, [name]: false }
    delete originalResolverValues.value[name]
    
    // 显示成功提示
    alert('解析器更新成功')
  } catch (error) {
    console.error('更新解析器失败:', error)
    alert(`更新解析器失败: ${error instanceof Error ? error.message : '未知错误'}`)
    // 发生错误时恢复原始值
    cancelResolverEdit(name)
  }
}

/* ---------------------- 删除节点 ---------------------- */

async function deleteForwarderNode(node: ForwarderNode) {
  if (!confirm(`确定要删除转发器 "${node.name}" 吗？这将同时删除其下所有解析器。`)) {
    return
  }

  try {
    // 先删除所有解析器
    for (const resolver of node.backends_detailed) {
      try {
        const response = await fetch('/api/docker/delete_node', {
          method: 'POST',
          headers: {
            'Content-Type': 'application/json',
            'Accept': 'application/json'
          },
          body: JSON.stringify({
            node_name: resolver.name
          })
        })

        if (!response.ok) {
          console.warn(`删除解析器 ${resolver.name} 失败:`, response.status)
        }
      } catch (error) {
        console.error(`删除解析器 ${resolver.name} 失败:`, error)
      }
    }

    // 删除转发器本身
    const response = await fetch('/api/docker/delete_node', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Accept': 'application/json'
      },
      body: JSON.stringify({
        node_name: node.name
      })
    })

    if (!response.ok) {
      const errorText = await response.text()
      throw new Error(`HTTP error! status: ${response.status}, message: ${errorText}`)
    }

    const result = await response.json()
    console.log('删除转发器成功:', result)

    // 从本地列表中移除
    const idx = forwarderNodes.value.findIndex(n => n.name === node.name)
    if (idx !== -1) {
      forwarderNodes.value.splice(idx, 1)
      if (selectedForwarderIndex.value >= forwarderNodes.value.length) {
        selectedForwarderIndex.value = Math.max(0, forwarderNodes.value.length - 1)
      }
    }

    // 重新加载节点列表以确保数据同步
    await loadNodesFromBackend()
    
    alert('转发器删除成功')
  } catch (error) {
    console.error('删除转发器失败:', error)
    alert(`删除转发器失败: ${error instanceof Error ? error.message : '未知错误'}`)
  }
}

async function deleteResolverNode(resolver: ResolverNode, parent: ForwarderNode) {
  if (!confirm(`确定要删除解析器 "${resolver.name}" 吗？`)) {
    return
  }

  try {
    const response = await fetch('/api/docker/delete_node', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Accept': 'application/json'
      },
      body: JSON.stringify({
        node_name: resolver.name
      })
    })

    if (!response.ok) {
      const errorText = await response.text()
      throw new Error(`HTTP error! status: ${response.status}, message: ${errorText}`)
    }

    const result = await response.json()
    console.log('删除解析器成功:', result)

    // 从本地列表中移除
    const list = parent.backends_detailed
    const idx = list.findIndex(b => b.name === resolver.name)
    if (idx !== -1) {
      list.splice(idx, 1)
      parent.params.backend_count = list.length
    }

    // 重新加载节点列表以确保数据同步
    await loadNodesFromBackend()
    
    alert('解析器删除成功')
  } catch (error) {
    console.error('删除解析器失败:', error)
    alert(`删除解析器失败: ${error instanceof Error ? error.message : '未知错误'}`)
  }
}

/* ---------------------- 创建节点弹窗 ---------------------- */

const showCreate = ref(false)
const form = ref({
  policy: 'roundrobin',
  resolvers: [
    {
      resolver_nonbackoff_tries: 10,
      max_ncache_ttl: 0
    }
  ]
})

// 轮询策略选项
const policyOptions = [
  { value: 'leastOutstanding', label: 'leastOutstanding' },
  { value: 'firstAvailable', label: 'firstAvailable' },
  { value: 'wrandom', label: 'wrandom' },
  { value: 'whashed', label: 'whashed' },
  { value: 'chashed', label: 'chashed' },
  { value: 'roundrobin', label: 'roundrobin' },
  { value: 'orderedWrandUntag', label: 'orderedWrandUntag' },
]

function resetForm() {
  form.value = {
    policy: 'roundrobin',
    resolvers: [
      {
        resolver_nonbackoff_tries: 10,
        max_ncache_ttl: 0
      }
    ]
  }
}

function addResolver() {
  form.value.resolvers.push({
    resolver_nonbackoff_tries: 10,
    max_ncache_ttl: 0
  })
}

function removeResolver(index: number) {
  if (form.value.resolvers.length > 1) {
    form.value.resolvers.splice(index, 1)
  } else {
    window.alert('至少需要保留一个解析器')
  }
}

async function createForwarder() {
  const f = form.value
  if (!f.policy) {
    window.alert('请选择轮询策略')
    return
  }

  // 验证解析器数据
  if (!f.resolvers || f.resolvers.length === 0) {
    window.alert('请至少添加一个解析器')
    return
  }

  for (let i = 0; i < f.resolvers.length; i++) {
    const resolver = f.resolvers[i]
    if (resolver.resolver_nonbackoff_tries === undefined || resolver.resolver_nonbackoff_tries === null) {
      window.alert(`第 ${i + 1} 个解析器的重发次数不能为空`)
      return
    }
    if (resolver.max_ncache_ttl === undefined || resolver.max_ncache_ttl === null) {
      window.alert(`第 ${i + 1} 个解析器的最大负缓存不能为空`)
      return
    }
  }

  try {
    // 构建请求数据
    const requestData = [
      {
        forwarder_policy: f.policy,
        resolvers: f.resolvers.map(r => ({
          resolver_nonbackoff_tries: r.resolver_nonbackoff_tries,
          max_ncache_ttl: r.max_ncache_ttl
        }))
      }
    ]

    const response = await fetch('/api/docker/create_nodes', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Accept': 'application/json'
      },
      body: JSON.stringify(requestData)
    })

    if (!response.ok) {
      const errorText = await response.text()
      throw new Error(`HTTP error! status: ${response.status}, message: ${errorText}`)
    }

    const result = await response.json()
    console.log('创建转发器成功:', result)

    showCreate.value = false
    resetForm()

    // 重新加载节点列表
    await loadNodesFromBackend()
    
    alert('转发器创建成功')
  } catch (error) {
    console.error('创建转发器失败:', error)
    alert(`创建转发器失败: ${error instanceof Error ? error.message : '未知错误'}`)
  }
}

</script>

<template>
  <div class="nodes-page">
    <!-- 顶部：与仪表盘统一的标题区域 -->
    <header class="nodes-page-header">
      <div class="page-header-left">
        <h1 class="page-title">
          节点概览
          <span class="page-badge">系统节点监控</span>
        </h1>
        <p class="page-subtitle">
          统一管理转发器与解析器节点，支持快速查看、编辑与扩展，并展示转发器与解析器的绑定关系。
        </p>
      </div>
      <div class="page-header-actions">
        <button 
          class="btn btn-outline" 
          @click="loadNodesFromBackend"
          :disabled="loadingNodes"
        >
          {{ loadingNodes ? '加载中...' : '刷新节点' }}
        </button>
        <button class="btn btn-primary" @click="showCreate = true">
          <span class="btn-plus">＋</span>
          创建转发器
        </button>
      </div>
    </header>

    <div class="nodes-content-wrapper">
      <!-- 错误提示 -->
      <div v-if="nodesError" class="error-message">
        <span class="error-icon">⚠️</span>
        <span class="error-text">{{ nodesError }}</span>
        <button class="btn btn-xs btn-outline" @click="loadNodesFromBackend">重试</button>
      </div>

      <!-- 主内容区域 -->
      <div class="nodes-main-content">
        <div class="nodes-section-group" v-if="selectedForwarder">
      <!-- 当前选中转发器信息 -->
      <section class="panel-card panel-frontend">
        <div class="panel-header">
          <div class="panel-header-main">
            <div class="title-row">
              <span class="chip chip-primary">转发器</span>
              <span class="card-title">{{ selectedForwarder.name }}</span>
            </div>
            <div class="card-meta">
              <span>IP {{ selectedForwarder.ip }}</span>
              <span class="dot"></span>
              <span>dnsdist {{ selectedForwarder.version }}</span>
            </div>
          </div>

          <!-- 转发器操作 -->
          <div class="panel-actions">
            <button
              class="btn btn-outline"
              v-if="!editingForwarder"
              @click="startForwarderEdit"
            >
              修改
            </button>
            <template v-else>
              <button class="btn btn-success" @click="saveForwarderEdit">保存</button>
              <button class="btn btn-ghost" @click="cancelForwarderEdit">取消</button>
            </template>
            <button class="btn btn-danger" @click="deleteForwarderNode(selectedForwarder)">
              删除
            </button>
          </div>
        </div>

        <!-- 转发器表单 -->
        <div class="panel-body">
          <div class="field-grid">
            <div class="field-item">
              <div class="field-label">节点 IP</div>
              <div class="field-value">
                <input
                  v-model="selectedForwarder.ip"
                  disabled
                  class="input input-readonly"
                />
              </div>
            </div>
            <div class="field-item">
              <div class="field-label">软件版本</div>
              <div class="field-value">
                <input
                  v-model="selectedForwarder.version"
                  disabled
                  class="input input-readonly"
                />
              </div>
            </div>
            <div class="field-item field-wide">
              <div class="field-label">轮询策略</div>
              <div class="field-value">
                <select
                  v-model="selectedForwarder.params.policy"
                  :disabled="!editingForwarder"
                  class="input"
                >
                  <option v-for="opt in policyOptions" :key="opt.value" :value="opt.value">
                    {{ opt.label }}
                  </option>
                </select>
              </div>
            </div>
            <div class="field-item">
              <div class="field-label">解析器数量</div>
              <div class="field-value">
                <input
                  :value="selectedForwarder.params.backend_count"
                  disabled
                  class="input input-readonly"
                />
              </div>
            </div>
          </div>
        </div>
      </section>

      <!-- 解析器列表 -->
      <section class="panel-card panel-backends">
        <div class="panel-header">
          <div class="panel-header-main">
            <div class="title-row">
              <span class="chip chip-green">解析器</span>
              <span class="card-title">关联的解析器</span>
            </div>
            <div class="card-meta">
              <span>共 {{ totalResolvers }} 个解析器</span>
              <span class="dot"></span>
              <span>绑定到转发器：{{ selectedForwarder.name }}</span>
            </div>
          </div>
        </div>

        <div class="panel-body">
          <div class="backends-grid">
            <div
              v-for="resolver in selectedForwarder.backends_detailed"
              :key="resolver.name"
              class="backend-card"
              :class="{ editing: isResolverEditing(resolver.name) }"
            >
              <div class="backend-header">
                <div class="backend-header-main">
                  <div class="backend-tag">
                    <span class="chip chip-soft-green">解析器</span>
                  </div>
                  <div class="backend-title">{{ resolver.name }}</div>
                  <div class="backend-meta">
                    <span>IP {{ resolver.ip }}</span>
                    <span class="dot"></span>
                    <span>Bind9 {{ resolver.version }}</span>
                  </div>
                </div>
                <div class="backend-actions">
                  <button
                    v-if="!isResolverEditing(resolver.name)"
                    class="btn btn-xs btn-outline"
                    @click="startResolverEdit(resolver.name)"
                  >
                    修改
                  </button>
                  <template v-else>
                    <button
                      class="btn btn-xs btn-success"
                      @click="saveResolverEdit(resolver.name)"
                    >
                      保存
                    </button>
                    <button
                      class="btn btn-xs btn-ghost"
                      @click="cancelResolverEdit(resolver.name)"
                    >
                      取消
                    </button>
                  </template>
                  <button
                    class="btn btn-xs btn-danger"
                    @click="deleteResolverNode(resolver, selectedForwarder)"
                  >
                    删除
                  </button>
                </div>
              </div>

              <div class="backend-body">
                <div class="field-grid backend-fields">
                  <div class="field-item field-wide">
                    <div class="field-label">节点 IP</div>
                    <div class="field-value">
                      <input
                        v-model="resolver.ip"
                        disabled
                        class="input input-readonly"
                      />
                    </div>
                  </div>

                  <div class="field-item">
                    <div class="field-label">软件版本</div>
                    <div class="field-value">
                      <input
                        v-model="resolver.version"
                        disabled
                        class="input input-readonly"
                      />
                    </div>
                  </div>

                  <div class="field-item">
                    <div class="field-label">重发次数</div>
                    <div class="field-value">
                      <input
                        v-model.number="resolver.params.retries"
                        type="number"
                        :disabled="!isResolverEditing(resolver.name)"
                        class="input"
                      />
                    </div>
                  </div>

                  <div class="field-item field-wide">
                    <div class="field-label">最大负缓存 (秒)</div>
                    <div class="field-value">
                      <input
                        v-model.number="resolver.params.max_ncache_ttl"
                        type="number"
                        :disabled="!isResolverEditing(resolver.name)"
                        class="input"
                      />
                    </div>
                  </div>
                </div>
              </div>
            </div>

            <!-- 若无解析器的占位卡片 -->
            <div
              v-if="selectedForwarder.backends_detailed.length === 0"
              class="backend-empty"
            >
              当前转发器尚未关联任何解析器。
            </div>
          </div>
        </div>
      </section>
    </div>

        <!-- 没有转发器的占位 -->
        <section v-else class="panel-card panel-empty">
          <p>当前暂无转发器，请点击右上角 "创建转发器"。</p>
        </section>
      </div>

      <!-- 右侧转发器列表 -->
      <aside class="nodes-sidebar">
        <div class="sidebar-header">
          <h3>转发器列表</h3>
        </div>
        <div class="node-list">
          <div
            v-for="(node, index) in forwarderNodes"
            :key="node.name"
            class="node-item"
            :class="{ active: index === selectedForwarderIndex }"
            @click="selectedForwarderIndex = index"
          >
            <div class="node-content">
              <div class="node-info">
                <div class="node-header">
                  <strong>{{ node.name }}</strong>
                </div>
                <div class="node-details">
                  <span class="resolver-count">解析器 × {{ node.params.backend_count }}</span>
                </div>
                <div class="ip-info-small">IP: {{ node.ip }}</div>
              </div>
            </div>
          </div>
          <div v-if="forwarderNodes.length === 0" class="node-empty">
            暂无转发器
          </div>
        </div>
      </aside>
    </div>

    <!-- 创建转发器模态框 -->
    <div v-if="showCreate" class="modal">
      <div class="modal-content">
        <button class="modal-close" @click="showCreate = false">×</button>
        <h2 class="modal-title">创建转发器</h2>

        <form @submit.prevent="createForwarder">
          <div class="form-group">
            <label class="form-label">轮询策略</label>
            <select v-model="form.policy" class="form-control">
              <option v-for="opt in policyOptions" :key="opt.value" :value="opt.value">
                {{ opt.label }}
              </option>
            </select>
          </div>

          <div class="form-group">
            <div class="form-label-group">
              <label class="form-label">解析器列表</label>
              <button
                type="button"
                class="btn btn-xs btn-outline"
                @click="addResolver"
              >
                + 添加解析器
              </button>
            </div>
            
            <div class="resolvers-list">
              <div
                v-for="(resolver, index) in form.resolvers"
                :key="index"
                class="resolver-item"
              >
                <div class="resolver-header">
                  <span class="resolver-number">解析器 {{ index + 1 }}</span>
                  <button
                    type="button"
                    class="btn btn-xs btn-danger"
                    @click="removeResolver(index)"
                    :disabled="form.resolvers.length === 1"
                  >
                    删除
                  </button>
                </div>
                <div class="resolver-fields">
                  <div class="form-group-inline">
                    <label class="form-label-inline">重发次数</label>
                    <input
                      v-model.number="resolver.resolver_nonbackoff_tries"
                      type="number"
                      class="form-control-inline"
                      min="0"
                    />
                  </div>
                  <div class="form-group-inline">
                    <label class="form-label-inline">最大负缓存 (秒)</label>
                    <input
                      v-model.number="resolver.max_ncache_ttl"
                      type="number"
                      class="form-control-inline"
                      min="0"
                    />
                  </div>
                </div>
              </div>
            </div>
          </div>

          <div class="form-actions">
            <button
              type="button"
              class="btn btn-ghost"
              @click="showCreate = false"
            >
              取消
            </button>
            <button type="submit" class="btn btn-primary">创建</button>
          </div>
        </form>
      </div>
    </div>
  </div>
</template>

<style scoped>
/* 整体容器：保持和首页类似的主内容宽度 */
.nodes-page {
  padding: 24px 32px 32px;
  box-sizing: border-box;
  padding-right: 280px; /* 为右侧节点列表留出空间 */
}

/* 内容包装器 */
.nodes-content-wrapper {
  display: flex;
  gap: 16px;
  align-items: flex-start;
}

/* 主内容区域 */
.nodes-main-content {
  flex: 1;
  min-width: 0;
}

/* 右侧节点列表 */
.nodes-sidebar {
  width: 260px;
  flex-shrink: 0;
  border-left: 2px solid #e5e7eb;
  padding: 10px;
  overflow: hidden;
  position: fixed;
  top: 44px;
  right: 0;
  bottom: 60px;
  z-index: 10;
  background: linear-gradient(180deg, #ffffff 0%, #f9fafb 100%);
  box-shadow: -2px 0 8px rgba(0, 0, 0, 0.05);
  display: flex;
  flex-direction: column;
}

.sidebar-header {
  display: flex;
  flex-direction: column;
  margin-bottom: 16px;
  padding: 12px;
  background: linear-gradient(135deg, #667eea15, #764ba215);
  border-radius: 8px;
  border: 1px solid #e5e7eb;
}

.sidebar-header h3 {
  margin: 0;
  font-size: 15px;
  font-weight: 700;
  background: linear-gradient(135deg, #667eea, #764ba2);
  -webkit-background-clip: text;
  -webkit-text-fill-color: transparent;
  background-clip: text;
}

.node-list {
  flex: 1;
  overflow-y: auto;
  min-height: 0;
  padding-top: 2px; /* 为第一个节点的hover效果留出空间 */
  padding-bottom: 2px;
}

.node-item {
  border-radius: 8px;
  border: 1px solid #e5e7eb;
  background: #ffffff;
  padding: 10px;
  margin-bottom: 8px;
  cursor: pointer;
  transition: all 0.2s ease;
  position: relative; /* 为hover效果提供定位上下文 */
}

.node-item:hover {
  background: #f9fafb;
  border-color: #c7d2fe;
  transform: translateY(-1px);
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
  z-index: 1; /* 确保hover时在上层 */
}

.node-item.active {
  background: linear-gradient(135deg, #eef2ff, #f3e8ff);
  border-color: #6366f1;
  box-shadow: 0 2px 8px rgba(99, 102, 241, 0.2);
}

.node-content {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.node-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
}

.node-header strong {
  font-size: 13px;
  font-weight: 600;
  color: #111827;
}

.node-details {
  font-size: 12px;
  color: #6b7280;
}

.resolver-count {
  color: #10b981;
  font-weight: 500;
}

.ip-info-small {
  font-size: 11px;
  color: #9ca3af;
  font-family: monospace;
}

.node-empty {
  text-align: center;
  padding: 20px;
  color: #9ca3af;
  font-size: 13px;
}

/* 顶部标题区（和仪表盘统一） */
.nodes-page-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 18px;
}

.page-header-left {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.page-title {
  margin: 0;
  font-size: 20px;
  font-weight: 700;
  color: #111827;
  display: flex;
  align-items: center;
  gap: 10px;
}

.page-badge {
  padding: 4px 10px;
  border-radius: 999px;
  font-size: 12px;
  font-weight: 500;
  background: #eef2ff;
  color: #4f46e5;
  border: 1px solid #c7d2fe;
}

.page-subtitle {
  margin: 0;
  font-size: 13px;
  color: #6b7280;
}

.page-header-actions {
  display: flex;
  align-items: center;
  gap: 10px;
}

/* 错误提示 */
.error-message {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 12px 16px;
  background: #fef2f2;
  border: 1px solid #fecaca;
  border-radius: 8px;
  margin-bottom: 16px;
  color: #dc2626;
  font-size: 13px;
}

.error-icon {
  font-size: 16px;
}

.error-text {
  flex: 1;
}

/* 将前端+后端视为一个组合区域 */
.nodes-section-group {
  margin-top: 4px;
}

/* 通用 panel 卡片 */
.panel-card {
  position: relative;
  margin-top: 16px;
  border-radius: 16px;
  border: 1px solid #e5e7eb;
  background: #ffffff;
  box-shadow: 0 12px 30px rgba(15, 23, 42, 0.06);
  padding: 18px 22px 22px;
  box-sizing: border-box;
  max-width: 1320px;
  margin-left: auto;
  margin-right: auto;
}

/* 顶部渐变细条 */
.panel-card::before {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  top: 0;
  height: 3px;
  border-radius: 16px 16px 0 0;
  background: linear-gradient(90deg, #6366f1, #8b5cf6, #ec4899);
}

/* 前端 panel 下方连一根竖线，连接到后端 panel，体现绑定 */
.panel-frontend {
  margin-top: 8px;
  padding-top: 20px;
}

.panel-frontend::after {
  content: '';
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
  bottom: -22px;
  width: 2px;
  height: 26px;
  background: linear-gradient(to bottom, #c7d2fe, #8b5cf6);
  opacity: 0.9;
}

/* panel header 布局 */
.panel-header {
  display: flex;
  align-items: center;
  gap: 16px;
  justify-content: space-between;
  flex-wrap: wrap;
}

.panel-header-main {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.title-row {
  display: flex;
  align-items: center;
  gap: 8px;
}

.card-title {
  font-size: 16px;
  font-weight: 600;
  color: #111827;
}

.card-meta {
  font-size: 12px;
  color: #6b7280;
  display: flex;
  align-items: center;
  gap: 6px;
}

.dot {
  width: 3px;
  height: 3px;
  border-radius: 999px;
  background: #d1d5db;
}

/* 前端切换器 */
.frontend-switcher {
  display: flex;
  align-items: center;
  gap: 8px;
}

.switcher-label {
  font-size: 12px;
  color: #6b7280;
}

.switcher-pills {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}

.pill {
  border-radius: 999px;
  padding: 6px 12px;
  border: 1px solid #e5e7eb;
  background: #f9fafb;
  font-size: 12px;
  cursor: pointer;
  color: #4b5563;
  transition: all 0.2s ease;
}

.pill:hover {
  background: #eef2ff;
  border-color: #c7d2fe;
  color: #4338ca;
}

.pill.active {
  background: #4f46e5;
  border-color: #4f46e5;
  color: #ffffff;
  box-shadow: 0 4px 10px rgba(79, 70, 229, 0.4);
}

/* panel 通用操作区 */
.panel-actions {
  display: flex;
  align-items: center;
  gap: 8px;
}

/* panel 内容 */
.panel-body {
  margin-top: 18px;
}

/* 字段网格布局 */
.field-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  column-gap: 24px;
  row-gap: 14px;
}

.field-item {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.field-item.field-wide {
  grid-column: 1 / -1;
}

.field-label {
  font-size: 13px;
  color: #4b5563;
}

.field-value {
  width: 100%;
}

/* 输入样式 */
.input {
  width: 100%;
  border-radius: 10px;
  border: 1px solid #e5e7eb;
  padding: 9px 12px;
  font-size: 13px;
  color: #111827;
  background: #f9fafb;
  outline: none;
  transition: all 0.15s ease;
  box-sizing: border-box;
}

.input:focus {
  border-color: #6366f1;
  background: #ffffff;
  box-shadow: 0 0 0 1px rgba(99, 102, 241, 0.35);
}

.input:disabled {
  background: #f9fafb;
  color: #6b7280;
  border-style: dashed;
}

.input-readonly {
  cursor: default;
}

/* 后端列表 grid，自适应列数，方便扩展 */
.panel-backends {
  margin-top: 34px;
}

.backends-grid {
  margin-top: 10px;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
  gap: 16px;
}

/* 后端卡片 */
.backend-card {
  border-radius: 14px;
  border: 1px solid #e5e7eb;
  background: radial-gradient(circle at top left, #eef2ff 0, #ffffff 40%, #ffffff 100%);
  box-shadow: 0 6px 18px rgba(15, 23, 42, 0.06);
  padding: 12px 14px 14px;
  display: flex;
  flex-direction: column;
  gap: 10px;
  transition: all 0.2s ease;
}

.backend-card:hover {
  transform: translateY(-2px);
  box-shadow: 0 10px 24px rgba(15, 23, 42, 0.12);
}

.backend-card.editing {
  box-shadow: 0 0 0 1px rgba(129, 140, 248, 0.7), 0 10px 26px rgba(15, 23, 42, 0.2);
}

.backend-header {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: 8px;
}

.backend-header-main {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.backend-title {
  font-size: 14px;
  font-weight: 600;
  color: #111827;
}

.backend-meta {
  font-size: 11px;
  color: #6b7280;
  display: flex;
  align-items: center;
  gap: 4px;
}

.backend-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
  justify-content: flex-end;
}

.backend-body {
  margin-top: 6px;
}

.backend-fields {
  grid-template-columns: 1fr 1fr;
}

/* 无后端占位 */
.backend-empty {
  border-radius: 12px;
  border: 1px dashed #d1d5db;
  padding: 16px;
  font-size: 13px;
  color: #6b7280;
  display: flex;
  align-items: center;
  justify-content: space-between;
}

/* Tag / Chip */
.chip {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 2px 8px;
  border-radius: 999px;
  font-size: 11px;
  font-weight: 500;
  border: 1px solid transparent;
}

.chip-primary {
  background: rgba(99, 102, 241, 0.12);
  border-color: rgba(99, 102, 241, 0.35);
  color: #4338ca;
}

.chip-green {
  background: rgba(16, 185, 129, 0.1);
  border-color: rgba(16, 185, 129, 0.4);
  color: #047857;
}

.chip-soft-green {
  background: rgba(187, 247, 208, 0.8);
  border-color: rgba(34, 197, 94, 0.5);
  color: #047857;
}

/* 按钮体系：统一全项目的视觉 */
.btn {
  position: relative;
  border-radius: 999px;
  border: 1px solid transparent;
  padding: 7px 14px;
  font-size: 12px;
  font-weight: 500;
  cursor: pointer;
  background: #f9fafb;
  color: #374151;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 4px;
  transition: all 0.15s ease;
  white-space: nowrap;
}

.btn:hover:not(.btn-primary):not(.btn-success):not(.btn-danger) {
  background: #e5e7eb;
}

.btn-xs {
  padding: 4px 10px;
  font-size: 11px;
}

.btn-primary {
  background: linear-gradient(135deg, #6366f1, #8b5cf6);
  color: #ffffff;
  box-shadow: 0 4px 12px rgba(99, 102, 241, 0.5);
}

.btn-primary:hover {
  box-shadow: 0 6px 16px rgba(99, 102, 241, 0.7);
  transform: translateY(-1px);
}

.btn-success {
  background: linear-gradient(135deg, #10b981, #22c55e);
  color: #ffffff;
  border-color: transparent;
}

.btn-success:hover {
  box-shadow: 0 4px 10px rgba(16, 185, 129, 0.5);
  transform: translateY(-1px);
}

.btn-outline {
  background: #ffffff;
  border-color: #d1d5db;
  color: #374151;
}

.btn-outline:hover {
  border-color: #6366f1;
  color: #4338ca;
  background: #eef2ff;
}

.btn-ghost {
  background: #f9fafb;
  border-color: #e5e7eb;
  color: #4b5563;
}

.btn-ghost:hover {
  background: #e5e7eb;
}

.btn-danger {
  background: linear-gradient(135deg, #ef4444, #dc2626);
  color: #ffffff;
  border-color: transparent;
}

.btn-danger:hover {
  box-shadow: 0 4px 10px rgba(239, 68, 68, 0.6);
  transform: translateY(-1px);
}

.btn-link {
  background: transparent;
  border-color: transparent;
  color: #4f46e5;
  padding: 0;
}

.btn-link:hover {
  text-decoration: underline;
  background: transparent;
}

.btn-plus {
  font-size: 14px;
}

/* 模态框 */
.modal {
  position: fixed;
  inset: 0;
  background: rgba(15, 23, 42, 0.45);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 1000;
  backdrop-filter: blur(3px);
}

.modal-content {
  width: 90%;
  max-width: 560px;
  background: linear-gradient(135deg, #ffffff 0, #f9fafb 60%, #eef2ff 100%);
  border-radius: 18px;
  padding: 24px 24px 22px;
  box-shadow: 0 20px 40px rgba(15, 23, 42, 0.4);
  position: relative;
  box-sizing: border-box;
}

.modal-title {
  margin: 0 0 18px 0;
  font-size: 18px;
  font-weight: 600;
  color: #111827;
}

.modal-close {
  position: absolute;
  right: 16px;
  top: 14px;
  width: 28px;
  height: 28px;
  border-radius: 999px;
  border: none;
  background: #f3f4f6;
  color: #6b7280;
  cursor: pointer;
  font-size: 18px;
  line-height: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: all 0.15s ease;
}

.modal-close:hover {
  background: #e5e7eb;
  color: #111827;
}

/* 表单 */
.form-group {
  margin-bottom: 14px;
}

.form-label {
  display: block;
  font-size: 13px;
  color: #4b5563;
  margin-bottom: 6px;
}

.form-control {
  width: 100%;
  border-radius: 10px;
  border: 1px solid #e5e7eb;
  padding: 9px 11px;
  font-size: 13px;
  box-sizing: border-box;
  background: #ffffff;
  outline: none;
  transition: all 0.15s ease;
}

.form-control:focus {
  border-color: #6366f1;
  box-shadow: 0 0 0 1px rgba(99, 102, 241, 0.35);
}

.form-actions {
  margin-top: 18px;
  display: flex;
  justify-content: flex-end;
  gap: 10px;
  border-top: 1px solid #e5e7eb;
  padding-top: 14px;
}

/* 解析器列表样式 */
.form-label-group {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 12px;
}

.resolvers-list {
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.resolver-item {
  border: 1px solid #e5e7eb;
  border-radius: 8px;
  padding: 12px;
  background: #f9fafb;
}

.resolver-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 10px;
}

.resolver-number {
  font-size: 13px;
  font-weight: 600;
  color: #374151;
}

.resolver-fields {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.form-group-inline {
  display: flex;
  align-items: center;
  gap: 8px;
}

.form-label-inline {
  font-size: 12px;
  color: #4b5563;
  min-width: 120px;
}

.form-control-inline {
  flex: 1;
  border-radius: 6px;
  border: 1px solid #e5e7eb;
  padding: 6px 10px;
  font-size: 13px;
  box-sizing: border-box;
  background: #ffffff;
  outline: none;
  transition: all 0.15s ease;
}

.form-control-inline:focus {
  border-color: #6366f1;
  box-shadow: 0 0 0 1px rgba(99, 102, 241, 0.35);
}

/* 响应式适配 */
@media (max-width: 1200px) {
  .nodes-page {
    padding-right: 280px;
  }
}

@media (max-width: 960px) {
  .nodes-page {
    padding-right: 32px;
  }

  .nodes-content-wrapper {
    flex-direction: column;
  }

  .nodes-sidebar {
    position: relative;
    top: auto;
    right: auto;
    bottom: auto;
    width: 100%;
    border-left: none;
    border-top: 2px solid #e5e7eb;
    max-height: 300px;
  }

  .nodes-page-header {
    flex-direction: column;
    align-items: flex-start;
    gap: 10px;
  }

  .panel-header {
    align-items: flex-start;
  }

  .field-grid {
    grid-template-columns: 1fr;
  }

  .backend-fields {
    grid-template-columns: 1fr;
  }
}

@media (max-width: 640px) {
  .nodes-page {
    padding: 16px 12px 24px;
  }

  .panel-card {
    padding: 14px 14px 18px;
  }

  .panel-actions {
    width: 100%;
    justify-content: flex-start;
    flex-wrap: wrap;
  }
}
</style>